/*
 * Copyright (c) JForum Team
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * This file creation date: Apr 23, 2003 / 10:46:05 PM
 * The JForum Project
 * http://www.jforum.net
 */
package net.jforum.repository;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import net.jforum.SessionFacade;
import net.jforum.cache.CacheEngine;
import net.jforum.cache.Cacheable;
import net.jforum.dao.CategoryDAO;
import net.jforum.dao.ConfigDAO;
import net.jforum.dao.DataAccessDriver;
import net.jforum.dao.ForumDAO;
import net.jforum.dao.UserDAO;
import net.jforum.entities.Category;
import net.jforum.entities.Config;
import net.jforum.entities.Forum;
import net.jforum.entities.LastPostInfo;
import net.jforum.entities.MostUsersEverOnline;
import net.jforum.entities.Post;
import net.jforum.entities.Topic;
import net.jforum.entities.User;
import net.jforum.exceptions.CategoryNotFoundException;
import net.jforum.exceptions.DatabaseException;
import net.jforum.security.PermissionControl;
import net.jforum.security.SecurityConstants;
import net.jforum.util.CategoryOrderComparator;
import net.jforum.util.preferences.ConfigKeys;
import net.jforum.util.preferences.SystemGlobals;

import org.apache.log4j.Logger;

/**
 * Repository for the forums of the System. This repository acts like a cache
 * system, to avoid repetitive and unnecessary SQL queries every time we need
 * some info about the forums. To start the repository, call the method
 * <code>start(ForumModel, CategoryModel)</code>
 * 
 * @author Rafael Steil
 * @version $Id: ForumRepository.java,v 1.60 2007/09/10 23:06:59 rafaelsteil Exp
 *          $
 */
public class ForumRepository implements Cacheable {
    private static CacheEngine cache;
    private static ForumRepository instance;
    private static Logger logger = Logger.getLogger(ForumRepository.class);

    private static final String FQN = "forumRepository";
    private static final String CATEGORIES_SET = "categoriesSet";
    private static final String RELATION = "relationForums";
    private static final String FQN_MODERATORS = FQN + "/moderators";
    private static final String TOTAL_MESSAGES = "totalMessages";
    private static final String MOST_USERS_ONLINE = "mostUsersEverOnline";
    private static final String LOADED = "loaded";
    private static final String LAST_USER = "lastUser";
    private static final String TOTAL_USERS = "totalUsers";

    /**
     * @see net.jforum.cache.Cacheable#setCacheEngine(net.jforum.cache.CacheEngine)
     */
    public void setCacheEngine(CacheEngine engine) {
	cache = engine;
    }

    /**
     * Starts the repository.
     * 
     * @param fm
     *            The <code>ForumModel</code> instance which will be used to
     *            retrieve information about the forums.
     * @param cm
     *            The <code>CategoryModel</code> instance which will be used to
     *            retrieve information about the categories.
     * @param configModel
     *            ConfigDAO
     */
    public synchronized static void start(ForumDAO fm, CategoryDAO cm,
	    ConfigDAO configModel) {
	instance = new ForumRepository();

	if (cache.get(FQN, LOADED) == null) {
	    instance.loadCategories(cm);
	    instance.loadForums(fm);
	    instance.loadMostUsersEverOnline(configModel);
	    instance.loadUsersInfo();

	    Integer i = (Integer) cache.get(FQN, TOTAL_MESSAGES);

	    if (i == null) {
		cache.add(FQN, TOTAL_MESSAGES, new Integer(0));
	    }

	    cache.add(FQN, LOADED, "1");
	}
    }

    /**
     * Gets a category by its id. A call to @link #getCategory(int, int) is
     * made, using the return of
     * <code>SessionFacade.getUserSession().getUserId()</code> as argument for
     * the "userId" parameter.
     * 
     * @param categoryId
     *            The id of the category to check
     * @return <code>null</code> if the category is either not found or access
     *         is denied.
     * @see #getCategory(int, int)
     */
    public static Category getCategory(int categoryId) {
	return getCategory(SessionFacade.getUserSession().getUserId(),
		categoryId);
    }

    /**
     * Gets a category by its id.
     * 
     * @param userId
     *            The user id who is requesting the category
     * @param categoryId
     *            The id of the category to get
     * @return <code>null</code> if the category is either not found or access
     *         is denied.
     * @see #getCategory(int)
     */
    public static Category getCategory(int userId, int categoryId) {
	if (!isCategoryAccessible(userId, categoryId)) {
	    return null;
	}

	return (Category) cache.get(FQN, Integer.toString(categoryId));
    }

    public static Category getCategory(PermissionControl pc, int categoryId) {
	if (!isCategoryAccessible(pc, categoryId)) {
	    return null;
	}

	return (Category) cache.get(FQN, Integer.toString(categoryId));
    }

    public static Category retrieveCategory(int categoryId) {
	return (Category) cache.get(FQN, Integer.toString(categoryId));
    }

    /**
     * Check is some category is accessible.
     * 
     * @param userId
     *            The user's id who is trying to get the category
     * @param categoryId
     *            The category's id to check for access rights
     * @return <code>true</code> if access to the category is allowed.
     */
    public static boolean isCategoryAccessible(int userId, int categoryId) {
	return isCategoryAccessible(SecurityRepository.get(userId), categoryId);
    }

    /**
     * Check if some category is accessible.
     * 
     * @param categoryId
     *            The category id to check for access rights
     * @return <code>true</code> if access to the category is allowed.
     */
    public static boolean isCategoryAccessible(int categoryId) {
	return isCategoryAccessible(SessionFacade.getUserSession().getUserId(),
		categoryId);
    }

    /**
     * Check is some category is accessible.
     * 
     * @param pc
     *            The <code>PermissionControl</code> instance containing all
     *            security info related to the user.
     * @param categoryId
     *            the category's id to check for access rights
     * @return <code>true</code> if access to the category is allowed.
     */
    public static boolean isCategoryAccessible(PermissionControl pc,
	    int categoryId) {
	return pc.canAccess(SecurityConstants.PERM_CATEGORY,
		Integer.toString(categoryId));
    }

    /**
     * Gets all categories from the cache.
     * 
     * @param userId
     *            int
     * @return <code>List</code> with the categories. Each entry is a
     *         <code>Category</code> object.
     */
    public static List getAllCategories(int userId) {
	PermissionControl pc = SecurityRepository.get(userId);
	List l = new ArrayList();

	Set categoriesSet = (Set) cache.get(FQN, CATEGORIES_SET);

	if (categoriesSet == null) {
	    synchronized (ForumRepository.instance) {
		if (categoriesSet == null) {
		    logger.warn("Categories set returned null from the cache. Trying to reload");

		    try {
			ForumRepository.instance
				.loadCategories(DataAccessDriver.getInstance()
					.newCategoryDAO());
			ForumRepository.instance.loadForums(DataAccessDriver
				.getInstance().newForumDAO());
		    } catch (Exception e) {
			throw new CategoryNotFoundException(
				"Failed to get the category", e);
		    }

		    categoriesSet = (Set) cache.get(FQN, CATEGORIES_SET);

		    if (categoriesSet == null) {
			throw new CategoryNotFoundException(
				"Could not find all categories. There must be a problem with the cache");
		    }
		}
	    }
	}

	for (Iterator iter = categoriesSet.iterator(); iter.hasNext();) {
	    Category c = getCategory(pc, ((Category) iter.next()).getId());

	    if (c != null) {
		l.add(c);
	    }
	}

	return l;
    }

    /**
     * Get all categories. A call to @link #getAllCategories(int) is made,
     * passing the return of
     * <code>SessionFacade.getUserSession().getUserId()</code> as the value for
     * the "userId" argument.
     * 
     * @return <code>List</code> with the categories. Each entry is a
     *         <code>Category</code> object.
     * @see #getAllCategories(int)
     */
    public static List getAllCategories() {
	return getAllCategories(SessionFacade.getUserSession().getUserId());
    }

    private static Category findCategoryByOrder(int order) {
	for (Iterator iter = ((Set) cache.get(FQN, CATEGORIES_SET)).iterator(); iter
		.hasNext();) {
	    Category c = (Category) iter.next();
	    if (c.getOrder() == order) {
		return c;
	    }
	}

	return null;
    }

    /**
     * Updates some category. This method only updated the "name" and "order"
     * fields.
     * 
     * @param c
     *            The category to update. The method will search for a category
     *            with the same id and update its data.
     */
    public synchronized static void reloadCategory(Category c) {
	Category current = (Category) cache.get(FQN,
		Integer.toString(c.getId()));
	Category currentAtOrder = findCategoryByOrder(c.getOrder());

	Set tmpSet = new TreeSet(new CategoryOrderComparator());
	tmpSet.addAll((Set) cache.get(FQN, CATEGORIES_SET));

	if (currentAtOrder != null) {
	    tmpSet.remove(currentAtOrder);
	    cache.remove(FQN, Integer.toString(currentAtOrder.getId()));
	}

	tmpSet.add(c);
	cache.add(FQN, Integer.toString(c.getId()), c);

	if (currentAtOrder != null && c.getId() != currentAtOrder.getId()) {
	    tmpSet.remove(current);
	    currentAtOrder.setOrder(current.getOrder());
	    tmpSet.add(currentAtOrder);

	    cache.add(FQN, Integer.toString(currentAtOrder.getId()),
		    currentAtOrder);
	}

	cache.add(FQN, CATEGORIES_SET, tmpSet);
    }

    /**
     * Refreshes a category entry in the cache.
     * 
     * @param c
     *            The category to refresh
     */
    public synchronized static void refreshCategory(Category c) {
	cache.add(FQN, Integer.toString(c.getId()), c);
	Set s = (Set) cache.get(FQN, CATEGORIES_SET);
	s.remove(c);
	s.add(c);
	cache.add(FQN, CATEGORIES_SET, s);
    }

    public synchronized static void refreshForum(Forum forum) {
	Category c = retrieveCategory(forum.getCategoryId());
	c.addForum(forum);
	refreshCategory(c);
    }

    /**
     * Remove a category from the cache
     * 
     * @param c
     *            The category to remove. The instance should have the category
     *            id at least
     */
    public synchronized static void removeCategory(Category c) {
	cache.remove(FQN, Integer.toString(c.getId()));

	Set s = (Set) cache.get(FQN, CATEGORIES_SET);
	s.remove(c);
	cache.add(FQN, CATEGORIES_SET, s);

	Map m = (Map) cache.get(FQN, RELATION);
	for (Iterator iter = m.values().iterator(); iter.hasNext();) {
	    if (Integer.parseInt((String) iter.next()) == c.getId()) {
		iter.remove();
	    }
	}

	cache.add(FQN, RELATION, m);
    }

    /**
     * Adds a new category to the cache.
     * 
     * @param c
     *            The category instance to insert in the cache.
     */
    public synchronized static void addCategory(Category c) {
	String categoryId = Integer.toString(c.getId());
	cache.add(FQN, categoryId, c);

	Set s = (Set) cache.get(FQN, CATEGORIES_SET);

	if (s == null) {
	    s = new TreeSet(new CategoryOrderComparator());
	}

	s.add(c);
	cache.add(FQN, CATEGORIES_SET, s);

	Map relation = (Map) cache.get(FQN, RELATION);
	if (relation == null) {
	    relation = new HashMap();
	}

	for (Iterator iter = c.getForums().iterator(); iter.hasNext();) {
	    Forum f = (Forum) iter.next();
	    relation.put(Integer.toString(f.getId()), categoryId);
	}

	cache.add(FQN, RELATION, relation);
    }

    /**
     * Gets a specific forum from the cache.
     * 
     * @param forumId
     *            The forum's ID to get
     * @return <code>net.jforum.Forum</code> object instance or
     *         <code>null</code> if the forum was not found or is not accessible
     *         to the user.
     */
    public static Forum getForum(int forumId) {
	String categoryId = (String) ((Map) cache.get(FQN, RELATION))
		.get(Integer.toString(forumId));

	if (categoryId != null) {
	    Category category = (Category) cache.get(FQN, categoryId);

	    if (isCategoryAccessible(category.getId())) {
		return category.getForum(forumId);
	    }
	}

	return null;
    }

    public static boolean isForumAccessible(int forumId) {
	return isForumAccessible(SessionFacade.getUserSession().getUserId(),
		forumId);
    }

    public static boolean isForumAccessible(int userId, int forumId) {
	int categoryId = Integer.parseInt((String) ((Map) cache.get(FQN,
		RELATION)).get(Integer.toString(forumId)));
	return isForumAccessible(userId, categoryId, forumId);
    }

    public static boolean isForumAccessible(int userId, int categoryId,
	    int forumId) {
	return ((Category) cache.get(FQN, Integer.toString(categoryId)))
		.getForum(userId, forumId) != null;
    }

    /**
     * Adds a new forum to the cache repository.
     * 
     * @param forum
     *            The forum to add
     */
    public synchronized static void addForum(Forum forum) {
	String categoryId = Integer.toString(forum.getCategoryId());

	Category c = (Category) cache.get(FQN, categoryId);
	c.addForum(forum);
	cache.add(FQN, categoryId, c);

	Map m = (Map) cache.get(FQN, RELATION);
	m.put(Integer.toString(forum.getId()), categoryId);
	cache.add(FQN, RELATION, m);

	Set s = (Set) cache.get(FQN, CATEGORIES_SET);
	cache.add(FQN, CATEGORIES_SET, s);
    }

    /**
     * Removes a forum from the cache.
     * 
     * @param forum
     *            The forum instance to remove.
     */
    public synchronized static void removeForum(Forum forum) {
	String id = Integer.toString(forum.getId());
	Map m = (Map) cache.get(FQN, RELATION);
	m.remove(id);
	cache.add(FQN, RELATION, m);

	id = Integer.toString(forum.getCategoryId());

	Category c = (Category) cache.get(FQN, id);
	c.removeForum(forum.getId());
	cache.add(FQN, id, c);

	Set s = (Set) cache.get(FQN, CATEGORIES_SET);
	cache.add(FQN, CATEGORIES_SET, s);
    }

    /**
     * Reloads a forum. The forum should already be in the cache and <b>SHOULD
     * NOT</b> have its order changed. If the forum's order was changed, then
     * you <b>MUST CALL</b> @link Category#changeForumOrder(Forum) <b>BEFORE</b>
     * calling this method.
     * 
     * @param forumId
     *            int The forum to reload its information
     */
    public static synchronized void reloadForum(int forumId) {
	Forum f = DataAccessDriver.getInstance().newForumDAO()
		.selectById(forumId);

	if (((Map) cache.get(FQN, RELATION)).containsKey(Integer
		.toString(forumId))) {
	    String id = Integer.toString(f.getCategoryId());
	    Category c = (Category) cache.get(FQN, id);

	    f.setLastPostInfo(null);
	    f.setLastPostInfo(ForumRepository.getLastPostInfo(f));
	    c.reloadForum(f);

	    cache.add(FQN, id, c);
	    Set s = (Set) cache.get(FQN, CATEGORIES_SET);
	    cache.add(FQN, CATEGORIES_SET, s);
	}

	getTotalMessages(true);
    }

    public static synchronized void updateForumStats(Topic t, User u, Post p) {
	String f = Integer.toString(t.getForumId());

	if (((Map) cache.get(FQN, RELATION)).containsKey(f)) {
	    Forum forum = getForum(t.getForumId());

	    SimpleDateFormat df = new SimpleDateFormat(
		    SystemGlobals.getValue(ConfigKeys.DATE_TIME_FORMAT));

	    LastPostInfo lpi = forum.getLastPostInfo();

	    if (lpi == null) {
		lpi = new LastPostInfo();
	    }

	    lpi.setPostId(p.getId());
	    lpi.setPostDate(df.format(p.getTime()));
	    lpi.setPostTimeMillis(p.getTime().getTime());
	    lpi.setTopicId(t.getId());
	    lpi.setTopicReplies(t.getTotalReplies());
	    lpi.setUserId(u.getId());
	    lpi.setUsername(u.getUsername());

	    forum.setLastPostInfo(lpi);

	    if (t.getTotalReplies() == 0) {
		forum.setTotalTopics(forum.getTotalTopics() + 1);
	    }

	    forum.setTotalPosts(forum.getTotalPosts() + 1);

	    Category c = retrieveCategory(forum.getCategoryId());
	    c.reloadForum(forum);

	    refreshCategory(c);
	}
    }

    /**
     * Gets information about the last message posted in some forum.
     * 
     * @param forum
     *            The forum to retrieve information
     * @return LastPostInfo
     */
    public static LastPostInfo getLastPostInfo(Forum forum) {
	LastPostInfo lpi = forum.getLastPostInfo();

	if (lpi == null || !forum.getLastPostInfo().hasInfo()) {
	    lpi = DataAccessDriver.getInstance().newForumDAO()
		    .getLastPostInfo(forum.getId());
	    forum.setLastPostInfo(lpi);
	}

	return lpi;
    }

    /**
     * Gets information about the last message posted in some forum.
     * 
     * @param forumId
     *            The forum's id to retrieve information
     * @return LastPostInfo
     */
    public static LastPostInfo getLastPostInfo(int forumId) {
	return getLastPostInfo(getForum(forumId));
    }

    /**
     * Gets information about the moderators of some forum.
     * 
     * @param forumId
     *            The forum to retrieve information
     * @return List
     */
    public static List getModeratorList(int forumId) {
	List l = (List) cache.get(FQN_MODERATORS, Integer.toString(forumId));

	if (l == null) {
	    synchronized (FQN_MODERATORS) {
		try {
		    l = DataAccessDriver.getInstance().newForumDAO()
			    .getModeratorList(forumId);
		    cache.add(FQN_MODERATORS, Integer.toString(forumId), l);
		} catch (Exception e) {
		    throw new DatabaseException(e);
		}
	    }
	}

	return l;
    }

    public static void clearModeratorList() {
	cache.remove(FQN_MODERATORS);
    }

    public static User lastRegisteredUser() {
	return (User) cache.get(FQN, LAST_USER);
    }

    public static void setLastRegisteredUser(User user) {
	cache.add(FQN, LAST_USER, user);
    }

    public static Integer totalUsers() {
	return (Integer) cache.get(FQN, TOTAL_USERS);
    }

    public static void incrementTotalUsers() {
	Integer i = (Integer) cache.get(FQN, TOTAL_USERS);

	if (i == null) {
	    i = new Integer(0);
	}

	cache.add(FQN, TOTAL_USERS, new Integer(i.intValue() + 1));
    }

    /**
     * Gets the number of messages in the entire board.
     * 
     * @return int
     * @see #getTotalMessages(boolean)
     */
    public static int getTotalMessages() {
	return getTotalMessages(false);
    }

    /**
     * Gets the number of messags in the entire board.
     * 
     * @param fromDb
     *            If <code>true</code>, a query to the database will be made, to
     *            retrieve the desired information. If <code>false</code>, the
     *            data will be fetched from the cache.
     * @return The number of messages posted in the board.
     * @see #getTotalMessages()
     */
    public static int getTotalMessages(boolean fromDb) {
	Integer i = (Integer) cache.get(FQN, TOTAL_MESSAGES);

	int total = i != null ? i.intValue() : 0;

	if (fromDb || total == 0) {
	    total = DataAccessDriver.getInstance().newForumDAO()
		    .getTotalMessages();
	    cache.add(FQN, TOTAL_MESSAGES, new Integer(total));
	}

	return total;
    }

    public static synchronized void incrementTotalMessages() {
	int total = ((Integer) cache.get(FQN, TOTAL_MESSAGES)).intValue();
	cache.add(FQN, TOTAL_MESSAGES, new Integer(total + 1));
    }

    /**
     * Gets the number of most online users ever
     * 
     * @return MostUsersEverOnline
     */
    public static MostUsersEverOnline getMostUsersEverOnline() {
	MostUsersEverOnline online = (MostUsersEverOnline) cache.get(FQN,
		MOST_USERS_ONLINE);

	if (online == null) {
	    synchronized (MOST_USERS_ONLINE) {
		online = (MostUsersEverOnline) cache
			.get(FQN, MOST_USERS_ONLINE);

		if (online == null) {
		    online = instance.loadMostUsersEverOnline(DataAccessDriver
			    .getInstance().newConfigDAO());
		}
	    }
	}

	return online;
    }

    /**
     * Update the value of most online users ever.
     * 
     * @param m
     *            MostUsersEverOnline The new value to store. Generally it will
     *            be a bigger one.
     */
    public static void updateMostUsersEverOnline(MostUsersEverOnline m) {
	ConfigDAO cm = DataAccessDriver.getInstance().newConfigDAO();
	Config config = cm.selectByName(ConfigKeys.MOST_USERS_EVER_ONLINE);

	if (config == null) {
	    // Total
	    config = new Config();
	    config.setName(ConfigKeys.MOST_USERS_EVER_ONLINE);
	    config.setValue(Integer.toString(m.getTotal()));

	    cm.insert(config);

	    // Date
	    config.setName(ConfigKeys.MOST_USER_EVER_ONLINE_DATE);
	    config.setValue(Long.toString(m.getTimeInMillis()));

	    cm.insert(config);
	} else {
	    // Total
	    config.setValue(Integer.toString(m.getTotal()));
	    cm.update(config);

	    // Date
	    config.setName(ConfigKeys.MOST_USER_EVER_ONLINE_DATE);
	    config.setValue(Long.toString(m.getTimeInMillis()));
	    cm.update(config);
	}

	cache.add(FQN, MOST_USERS_ONLINE, m);
    }

    /**
     * Loads all forums.
     * 
     * @param fm
     *            ForumDAO
     */
    private void loadForums(ForumDAO fm) {
	List l = fm.selectAll();

	Map m = (Map) cache.get(FQN, RELATION);
	if (m == null) {
	    m = new HashMap();
	}

	int lastId = 0;
	Category c = null;
	String catId = null;

	for (Iterator iter = l.iterator(); iter.hasNext();) {
	    Forum f = (Forum) iter.next();

	    if (f.getCategoryId() != lastId) {
		if (c != null) {
		    cache.add(FQN, catId, c);
		}

		lastId = f.getCategoryId();
		catId = Integer.toString(f.getCategoryId());
		c = (Category) cache.get(FQN, catId);
	    }

	    if (c == null) {
		throw new CategoryNotFoundException("Category for forum #"
			+ f.getId() + " not found");
	    }

	    String forumId = Integer.toString(f.getId());
	    c.addForum(f);
	    m.put(forumId, catId);
	}

	if (c != null) {
	    cache.add(FQN, catId, c);
	}

	cache.add(FQN, RELATION, m);
    }

    private void loadUsersInfo() {
	UserDAO udao = DataAccessDriver.getInstance().newUserDAO();
	cache.add(FQN, LAST_USER, udao.getLastUserInfo());
	cache.add(FQN, TOTAL_USERS, new Integer(udao.getTotalUsers()));
    }

    /**
     * Loads all categories.
     * 
     * @param cm
     *            CategoryDAO
     */
    private void loadCategories(CategoryDAO cm) {
	List categories = cm.selectAll();
	Set categoriesSet = new TreeSet(new CategoryOrderComparator());

	for (Iterator iter = categories.iterator(); iter.hasNext();) {
	    Category c = (Category) iter.next();

	    cache.add(FQN, Integer.toString(c.getId()), c);
	    categoriesSet.add(c);
	}

	cache.add(FQN, CATEGORIES_SET, categoriesSet);
    }

    private MostUsersEverOnline loadMostUsersEverOnline(ConfigDAO cm) {
	Config config = cm.selectByName(ConfigKeys.MOST_USERS_EVER_ONLINE);
	MostUsersEverOnline mostUsersEverOnline = new MostUsersEverOnline();

	if (config != null) {
	    mostUsersEverOnline.setTotal(Integer.parseInt(config.getValue()));

	    // We're assuming that, if we have one key, the another one
	    // will always exist
	    config = cm.selectByName(ConfigKeys.MOST_USER_EVER_ONLINE_DATE);
	    mostUsersEverOnline.setTimeInMillis(Long.parseLong(config
		    .getValue()));
	}

	cache.add(FQN, MOST_USERS_ONLINE, mostUsersEverOnline);

	return mostUsersEverOnline;
    }

    public static String getListAllowedForums() {
	int n = 0;
	StringBuffer buf = new StringBuffer();

	List allCategories = ForumRepository.getAllCategories();

	for (Iterator iter = allCategories.iterator(); iter.hasNext();) {
	    Collection forums = ((Category) iter.next()).getForums();

	    for (Iterator tmpIterator = forums.iterator(); tmpIterator
		    .hasNext();) {
		Forum f = (Forum) tmpIterator.next();

		if (ForumRepository.isForumAccessible(f.getId())) {
		    if (n++ > 0) {
			buf.append(',');
		    }

		    buf.append(f.getId());
		}
	    }
	}

	if (n <= 0) {
	    return "-1";
	}

	return buf.toString();
    }
}
